package sky.cin.framework.sfoc.core;

import sky.cin.framework.config.*;
import sky.cin.framework.log.*;
import sky.cin.framework.sfoc.core.annotations.beans.AnnotationContext;
import sky.cin.framework.sfoc.core.annotations.beans.Component;
import sky.cin.framework.sfoc.core.annotations.beans.Import;
import sky.cin.framework.sfoc.core.common.Context;
import sky.cin.framework.sfoc.core.common.exception.BeanException;
import sky.cin.framework.sfoc.core.common.exception.BeanNotFoundException;
import sky.cin.framework.sfoc.core.common.utils.BeanUtils;
import sky.cin.framework.sfoc.core.common.utils.EnvirUtils;
import sky.cin.framework.sfoc.core.interfaces.*;

import java.io.File;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Map;

public class AnnotationApplicationContext extends Context {
    
    private final BaseBeanCreator baseBeanCreator = new BaseBeanCreator(this);
    private final Croog log = new Croog("sky.cin.container.AnnotationContainer");
    private static final ConfigFile applicationConfig = new YamlUtils("application.yaml");
    
    public AnnotationApplicationContext(Class<?> clazz) {
        super.setConfigFile(applicationConfig);
        loadClass(clazz);
        defineBeans();
        initSingletonPool();
    }
    
    /**
     * 加载类
     * @param clazz 启动类
     */
    private void loadClass(Class<?> clazz) {
        ClassLoader classLoader = AnnotationApplicationContext.class.getClassLoader();
        if (!BeanUtils.isAnnotationPresent(clazz, AnnotationContext.class)) {
            throw new BeanException(clazz.getName() + " 不是一个启动器, 如果要指定为启动器应添加 @HTTPApplication 注解");
        }
        // 遍历启动类每一个注解，获取 @Import, 得到要定义的类
        for (Annotation starterAnno : clazz.getAnnotations()) {
            Import importAnno = BeanUtils.getAnnotation(starterAnno, Import.class);
            if (importAnno != null) {
                getClasses().addAll(Arrays.asList(importAnno.value()));
            }
        }
        // 解析启动类根目录路径
        File classFolder = EnvirUtils.getClassFolder(classLoader, clazz);
        log.info("类文件加载路径: " + classFolder.getAbsolutePath());
        // 读入路径下的所有 class 文件
        loadClass(classFolder, classLoader);
        log.info(String.format("扫描到 %d 个类", this.classes.size()));
        log.info(this.classes);
    }
    
    /**
     * 加载路径下所有类
     * @param classFolder 文件夹
     * @param classLoader 类加载器
     */
    private void loadClass(File classFolder, ClassLoader classLoader) {
        for (File classFile : classFolder.listFiles()) {
            if (classFile.isDirectory()) {
                loadClass(classFile, classLoader);
                continue;
            }
            String classPath = EnvirUtils.getClassPath(classFile);
            try {
                Class<?> aClass = classLoader.loadClass(classPath);
                classes.add(aClass);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 定义 bean 定义
     */
    private void defineBeans() {
        classes.forEach(this::defineBeans);
        log.info(String.format("其中 %d 个 bean", this.beanDefinitionMap.size()));
        log.info(this.beanDefinitionMap.keySet());
    }
    
    private void defineBeans(Class<?> clazz) {
        if (BeanUtils.isAnnotationPresent(clazz, Component.class)) {
            try {
                if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                    processors.add((BeanPostProcessor) clazz.newInstance());
                }
                if (BeanInterfaceFactory.class.isAssignableFrom(clazz)) {
                    factories.add((BeanInterfaceFactory) clazz.newInstance());
                }
                if (BeanCreator.class.isAssignableFrom(clazz)) {
                    creators.add((BeanCreator) clazz.newInstance());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            BeanDefinition sfocBeanDefinition = new SfocBeanDefinition(clazz);
            String beanName = BeanUtils.getBeanName(clazz);
            beanDefinitionMap.put(beanName, sfocBeanDefinition);
        }
    }
    
    /**
     * 初始化单例池（非懒加载）
     */
    private void initSingletonPool() {
        beanDefinitionMap.forEach((beanName, beanDefinition) -> {
            if (!beanDefinition.isLazy() && beanDefinition.isSingleton()) {
                getBean(beanName);
            }
        });
        log.info(String.format("创建了 %d 个单例对象", this.singletonPool.size()));
        log.info(this.singletonPool.keySet());
    }
    
    /**
     * 根据类型获取 bean, 如果在单例池找不到, 则生成 beanName 去继续获取
     * @param clazz 类型
     * @return bean
     */
    public Object getBean(Class<?> clazz) {
        for (Object value : singletonPool.values()) {
            if (value.getClass().equals(clazz)) {
                return value;
            }
        }
        String beanName = BeanUtils.getBeanName(clazz);
        return getBean(beanName);
    }
    
    /**
     * 根据 beanName 获取 bean
     * @param beanName beanName
     * @return bean
     */
    public Object getBean(String beanName) {
        // 单例模式先判断是否存在
        if (singletonPool.containsKey(beanName)) {
            return singletonPool.get(beanName);
        }
        BeanDefinition definition = beanDefinitionMap.get(beanName);
        // 没有定义
        if (definition == null) {
            throw new BeanNotFoundException(beanName);
        }
        // 创建对象
        Object bean = doCreateBean(beanName, definition);
        if (definition.isSingleton() && bean != null) {
            singletonPool.put(beanName, bean);
        }
        return bean;
    }
    
    private Object doCreateBean(String beanName, BeanDefinition definition) {
        // 创建源 bean
        Object sourceObj = baseBeanCreator.doCreateBean(beanName, definition);
        if (sourceObj == null) {
            return null;
        }
        // 遍历创建器
        for (BeanCreator creator : creators) {
            // 创建新的 bean
            sourceObj = creator.doCreateBean(beanName, definition, sourceObj);
            // 如果是 Map 类型需要直接返回, 无法继续创建
            if (sourceObj instanceof Map) {
                // 单例模式则需要将 kv 保存到单例池, 并置空, 防止后续将 Map 存入单例池
                if (definition.isSingleton()) {
                    Map<String, Object> map = (Map<String, Object>) sourceObj;
                    map.forEach(singletonPool::put);
                    sourceObj = null;
                }
                break;
            }
        }
        return sourceObj;
    }
    
    public ConfigFile getApplicationConfigFile() {
        return applicationConfig;
    }

}

